---
title: Agent state inputs and outputs
icon: "lucide/ArrowRightLeft"
description: Decide which state properties are received and returned to the frontend
---

## What is this?

Not all state properties are relevant for frontend-backend sharing.
This guide shows how to ensure only the right portion of state is communicated back and forth.

This guide is based on [LangGraph's Input/Output Schema feature](https://langchain-ai.github.io/langgraph/how-tos/input_output_schema/)

## When should I use this?

Depending on your implementation, some properties are meant to be processed internally, while some others are the way for the UI to communicate user input.
In addition, some state properties contain a lot of information. Syncing them back and forth between the agent and UI can be costly, while it might not have any practical benefit.

## Implementation

<Steps>
  <Step>
      ### Examine our old state
      LangGraph is stateful. As you transition between nodes, that state is updated and passed to the next node. For this example,
      let's assume that the state our agent should be using, can be described like this:
      <Tabs groupId="language_langgraph_agent" items={['Python', 'TypeScript']} default="Python" persist>
        <Tab value="Python">
        ```python title="agent.py"
        from copilotkit import CopilotKitState
        from typing import Literal

        class AgentState(CopilotKitState):
            question: str
            answer: str
            resources: List[str]
        ```
        </Tab>
        <Tab value="TypeScript">
        ```typescript title="agent-js/sample_agent/agent.ts"
        import { Annotation } from "@langchain/langgraph";
        import { CopilotKitStateAnnotation } from "@copilotkit/sdk-js/langgraph";

        const AgentState = Annotation.Root({
          ...CopilotKitStateAnnotation.spec,
          question: Annotation<string>,
          answer: Annotation<string>,
          resources: Annotation<string[]>,
        })
        ```
        </Tab>
    </Tabs>
  </Step>
  <Step>
    ### Divide state to Input and Output
    Our example case lists several state properties, which with its own purpose:
      - The question is being asked by the user, expecting the llm to answer
      - The answer is what the LLM returns
      - The resources list will be used by the LLM to answer the question, and should not be communicated to the user, or set by them.

      <Tabs groupId="language_langgraph_agent" items={['Python', 'TypeScript']} default="Python" persist>
          <Tab value="Python">
          ```python title="agent.py"
          from copilotkit import CopilotKitState
          from typing import Literal

          # Divide the state to 3 parts

          # Input schema for inputs you are willing to accept from the frontend
          class InputState(CopilotKitState):
            question: str

          # Output schema for output you are willing to pass to the frontend
          class OutputState(CopilotKitState):
            answer: str

          # The full schema, including the inputs, outputs and internal state ("resources" in our case)
          class OverallState(InputState, OutputState):
            resources: List[str]

          async def answer_node(state: OverallState, config: RunnableConfig):
            """
            Standard chat node, meant to answer general questions.
            """

            model = ChatOpenAI()

            # add the input question in the system prompt so it's passed to the LLM
            system_message = SystemMessage(
              content=f"You are a helpful assistant. Answer the question: {state.get('question')}"
            )

            response = await model.ainvoke([
              system_message,
              *state["messages"],
            ], config)

            # ...add the rest of the agent implementation

            # extract the answer, which will be assigned to the state soon
            answer = response.content

            return {
               "messages": response,
                # include the answer in the returned state
               "answer": answer
            }


          # finally, before compiling the graph, we define the 3 state components
          builder = StateGraph(OverallState, input=InputState, output=OutputState)

          # add all the different nodes and edges and compile the graph
          builder.add_node("answer_node", answer_node)
          builder.add_edge(START, "answer_node")
          builder.add_edge("answer_node", END)
          graph = builder.compile()
          ```
          </Tab>
          <Tab value="TypeScript">
            <Callout type="warn">
              While we work on adding Zod schema support for LangGraph TypeScript, a workaround is required to ignore a typescript error when defining "full state".

              You can see this at the very end of the TypeScript implementation snippet.
            </Callout>
            ```typescript title="agent-js/sample_agent/agent.ts"
              import { Annotation } from "@langchain/langgraph";
              import { CopilotKitStateAnnotation } from "@copilotkit/sdk-js/langgraph";

              // Divide the state to 3 parts

              // An input schema for inputs you are willing to accept from the frontend
              const InputAnnotation = Annotation.Root({
                ...CopilotKitStateAnnotation.spec,
                question: Annotation<string>,
              });

              // Output schema for output you are willing to pass to the frontend
              const OutputAnnotation = Annotation.Root({
                ...CopilotKitStateAnnotation.spec,
                answer: Annotation<string>,
              });

              // The full schema, including the inputs, outputs and internal state ("resources" in our case)
              export const AgentStateAnnotation = Annotation.Root({
                ...CopilotKitStateAnnotation.spec,
                ...OutputAnnotation.spec,
                ...InputAnnotation.spec,
                resources: Annotation<string[]>,
              });

              // Define a typed state that supports the entire
              export type AgentState = typeof AgentStateAnnotation.State;

              async function answerNode(state: AgentState, config: RunnableConfig) {
                const model = new ChatOpenAI()

                const systemMessage = new SystemMessage({
                  content: `You are a helpful assistant. Answer the question: ${state.question}.`,
                });

                const response = await modelWithTools.invoke(
                  [systemMessage, ...state.messages],
                  config
                );

                // ...add the rest of the agent implementation
                // extract the answer, which will be assigned to the state soon
                const answer = response.content

                return {
                  messages: response,
                  // include the answer in the returned state
                  answer,
                }
              }

              // finally, before compiling the graph, we define the 3 state components
              const workflow = new StateGraph({
                input: InputAnnotation,
                output: OutputAnnotation,
                // @ts-expect-error -- LangGraph does not expect a "full schema with internal properties".
                stateSchema: AgentStateAnnotation,
              })
                .addNode("answer_node", answerNode) // add all the different nodes and edges and compile the graph
                .addEdge(START, "answer_node")
                .addEdge("answer_node", END)
              export const graph = workflow.compile()
            ```
          </Tab>
      </Tabs>
  </Step>
  <Step>
    ### Give it a try!
    Now that we know which state properties our agent emits, we can inspect the state and expect the following to happen:
    - While we are able to provide a question, we will not receive it back from the agent. If we are using it in our UI, we need to remember the UI is the source of truth for it
    - Answer will change once it's returned back from the agent
    - The UI has no access to resources.

    ```tsx
    import { useCoAgent } from "@copilotkit/react-core";

    type AgentState = {
      question: string;
      answer: string;
    }

    const { state } = useCoAgent<AgentState>({
      name: "sample_agent",
      initialState: {
        question: "How's the weather in SF?",
      }
    });

    console.log(state) // You can expect seeing "answer" change, while the others are not returned from the agent
    ```
  </Step>
</Steps>
